<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
    <script src="./vue.global.js"></script>
    <script src="./marked.umd.js"></script>
    <link rel="stylesheet" href="./github-markdown-light.min.css">

    <style>
        body,
        html {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }

        #app {
            width: 100%;
            height: 100%;
            background-color: white;
            display: flex;
            justify-items: center;
            align-items: center;
            flex-direction: column;

        }

        .qwq-title {
            width: 100%;
            height: 38px;
            color: white;
            background-color: #2ed573;
            display: flex;
            justify-content: center;
            align-items: center;
            position: fixed;
            z-index: 999;
        }

        .message-container {
            position: relative;
            top: 60px;
            padding-bottom: 150px;
            width: 95%;
            max-width: 1000px;
            scrollbar-width: none;
            -ms-overflow-style: none;
            display: flex;
            flex-direction: column;
        }

        .user-message {
            align-self: flex-end;
            background-color: #DCF8C6;
            border-radius: 15px;
            width: fit-content;
            overflow: scroll;
            scrollbar-width: none;
            max-width: 100%;
        }

        .system-message {
            align-self: flex-start;
            background-color: transparent;
            border-radius: 15px;
            /* padding: 10px; */
            margin: 10px 0;
            width: 100%;
        }

        .message-container::-webkit-scrollbar {
            display: none;

        }

        .textarea-container {
            background-color: white;
            width: 100%;
            height: 80px;
            overflow: hidden;
            position: absolute;
            bottom: 0;
            padding: 20px 0;
        }

        .question-textarea {
            width: 100%;
            height: 100%;
            resize: none;
            border: 2px solid #2ed573;
            border-radius: 15px;
            padding: 10px;
            outline: none;
            background-color: transparent;
            /* overflow: hidden; */
            /* 隐藏滚动条但仍可滚动 */
            /* Firefox */
            scrollbar-width: none;
            /* IE 10+ */
            -ms-overflow-style: none;
            box-sizing: border-box;
            font-family: inherit;
            font-size: inherit;
            color: inherit;
        }

        @keyframes generating {
            10% {
                border-color: #FFD700;
            }

            20% {
                border-color: #FFA500;
            }

            30% {
                border-color: #FF6347;
            }

            40% {
                border-color: #FF4500;
            }

            50% {
                border-color: #FF0000;
            }

            60% {
                border-color: #DC143C;
            }

            70% {
                border-color: #CD5C5C;
            }

            80% {
                border-color: #FF69B4;
            }

            90% {
                border-color: #BA55D3;
            }

            100% {
                border-color: #8A2BE2;
            }
        }

        .is-generating {
            animation: generating 2s ease-in-out infinite;
        }

        .chatBox {
            background: transparent;
            width: 95%;
            max-width: 1000px;
            height: 135px;
            overflow: hidden;
            position: fixed;
            bottom: 0;
            padding-bottom: 20px;
        }

        .autoScrollBtn {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            position: absolute;
            background-color: #2ed573;
            right: 0;
            top: 5px;
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            font-size: 20px;
            z-index: 999;
            cursor: pointer;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="qwq-title">
            <h3>智能助手</h3>
        </div>
        <div class="message-container" ref="messageContainer">
            <div v-for="item in messageList" :key="item.role" class="message-content"
                v-bind:class="{'user-message': item.role === 'user','system-message': item.role === 'system'}">
                <div v-if="item.role === 'user'" style="margin: 1em;">{{ item.content }}</div>
                <div v-else-if="item.role === 'system'" v-html="parseMarkdown(item.content)"
                    style="margin: 1em; background: transparent !important;" class="markdown-body"></div>
            </div>
        </div>

        <div class="chatBox">
            <div class="autoScrollBtn" v-if="!isScrolledToBottom" @click="scrollToEnd">
                <!-- ⇩ -->
                ↆ
            </div>
            <div class="textarea-container">
                <textarea :disabled="isGenerating" class="question-textarea" :class="{'is-generating': isGenerating }"
                    :placeholder="isGenerating?'请稍后...':'请输入问题（输入clear清除历史）'" v-model="question"
                    @keydown.enter.prevent="doQuestion"></textarea>
            </div>
        </div>
    </div>
</body>

</html>
<script>
    const { createApp } = Vue
    let app = createApp({
        data() {
            return {
                question: "",
                // 生成进行中标识
                isGenerating: false,
                messageList: [
                    {
                        role: "system",
                        content: '你好，我是智能客服，请问你有什么问题'
                    },
                ],
                autoScroll: true,
                isScrolledToBottom: true,
            };
        },
        mounted() {
            this.getHistory()

            // 添加监听事件,如果用户手动向上滚动则关闭自动滚动
            window.addEventListener('wheel', (event) => app.calculateAutoScroll(event), { passive: false }); // passive:false 才能 preventDefault

            // 监听滚动事件
            window.addEventListener('scroll', this.calculateRolling)
            
            // 添加Ctrl+K快捷键事件，使输入框获得焦点
            window.addEventListener('keydown', (event) => {
                if (event.ctrlKey && event.key === 'k') {
                    event.preventDefault();
                    const textarea = document.querySelector('.question-textarea');
                    if (textarea) {
                        textarea.focus();
                    }
                }
            });

        },
        methods: {
            getHistory() {
                fetch('/qwq-ai/history')
                    .then(response => response.json())
                    .then(data => {
                        if (data?.code === 0 && data?.data?.messages?.length) {
                            this.messageList.length = 0
                            app.messageList = data?.data?.messages
                        }
                    })
            },
            parseMarkdown(markdown) {
                // marked.setOptions({
                //     breaks: true, // 允许 \n 被转换为 <br>
                //     gfm: true     // 启用 GitHub Flavored Markdown
                // });
                return marked.parse(markdown);
            },
            scrollToEnd() {
                this.autoScroll = true
                // 滚动到最底部
                this.$nextTick(() => {
                    // const el = this.$refs.messageContainer;
                    // el.scrollTop = el.scrollHeight;
                    window.scrollTo({
                        top: document.body.scrollHeight,
                        behavior: 'smooth'   // 平滑效果
                    });
                });
            },
            doQuestion() {
                // 判断是否按下shift，如果按下了 Shift 键，则执行换行操作
                if (event.shiftKey) {
                    const { selectionStart, selectionEnd, value } = event.target;
                    this.question = value.substring(0, selectionStart) + '\n' + value.substring(selectionEnd);

                    // 将光标定位到换行后的位置
                    this.$nextTick(() => {
                        event.target.selectionStart = event.target.selectionEnd = selectionStart + 1;
                    });
                    return
                }
                // 如果内容为空
                if (this.question?.trim() === "" || this.isGenerating) {
                    return;
                }
                this.isGenerating = true

                this.messageList.push({
                    role: "user",
                    content: this.question
                })

                // 滚动到最底部
                this.scrollToEnd()

                let es = new EventSource(`/qwq-ai/chat?q=${encodeURIComponent(this.question)}`)

                // 服务器每推一条就追加到 system 的最后一条消息里（流式效果）
                let systemMessageIndex = this.messageList.push({ role: 'system', content: '' }) - 1;

                es.onmessage = e => {
                    if (this.question === "clear") {
                        this.messageList.length = 0
                        return
                    }
                    let res = JSON.parse(e.data)
                    this.messageList[systemMessageIndex].content += res?.data;
                    if (this.autoScroll) {
                        this.scrollToEnd()
                    }

                };

                es.onerror = () => {
                    this.messageList[systemMessageIndex].content += '连接失败，请重试！'
                    es.close();
                    this.isGenerating = false;
                };

                // 如果服务器会发一个 event: end 作为结束标记
                es.addEventListener('end', () => {
                    es.close();
                    this.isGenerating = false;
                    this.question = "";
                });

            },
            calculateAutoScroll(event) {
                const deltaY = event.deltaY; // 垂直滚动量
                const deltaX = event.deltaX; // 水平滚动量
                if (!app.isGenerating) {
                    return
                }
                if (deltaY > 0) {
                    if (app.isScrolledToBottom) {
                        app.autoScroll = true
                    }
                } else if (deltaY < 0) {
                    app.autoScroll = false
                }
                // event.preventDefault();// 阻止默认行为(滚动)
            },
            // 计算滚动位置
            calculateRolling() {
                const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
                const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
                const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
                // console.log(`scrollTop:${scrollTop}, clientHeight:${clientHeight}, scrollHeight:${scrollHeight}`)
                this.isScrolledToBottom = scrollTop + clientHeight >= scrollHeight - 80;
            }
        },
    }).mount('#app')
</script>